本篇大綱:transition( ) 移動、變換顏色、transition.delay( )、transition.ease( )、transition.on( )
今天要來講講我覺得 D3.js 最有趣的部分啦!動畫效果基本上是酷炫前端網站必不可少的功能,D3 也知道這一點,並開發出一系列處理動畫效果的 API 們
一般主要是使用 selection.transition( ) 這個 API 來處理動畫,.transition() 歸類在selection 底下是因為它的設計邏輯是由d3.selection延伸而來,因此要先用select() 的方法選定 DOM 元素後,才能將動畫綁定到回傳的 selection 實體上。
一旦把所選的 DOM 元素加上 transition( ) 之後,就能建立d3的動畫效果。特別的是,這個動畫效果還多了動畫執行時間
、動畫生命週期
這些特性,因此我們就可以用其他 transition( ) 旗下的 API 來調整動畫的時長、動畫方式、延遲時間等等。我們能使用的 API 包含:
transition() 的用法也非常簡單,只要選定要加動畫的DOM元素之後,加上.transition(),接著在後方接上想用動畫完成的項目,這些項目就會被加上動畫啦~這邊要特別說一下,因為下一篇才會講到d3的事件觸發,所以這篇的範例我們都先用JS原生的on click事件去觸發動畫,下一篇開始再跟大家說要如何使用d3方便的事件觸發 API 們~
移動
以下就用這個例子來示範,目前畫面上[0,0]的位置有一個方塊,按下移動的按鈕後,這個方塊會被移動到[140,60]的位置
// html
<svg class="move"></svg>
<button type="button" class="btn btn-primary moveBtn">移動</button>
// js
// 方塊大小
const rect = d3.select('.move')
.append('rect')
.attr('width', 40)
.attr('height', 40)
.attr('stroke', 'black')
// 移動事件觸發
document.querySelector('.moveBtn').addEventListener('click', function(){
rect.attr('transform', 'translate(140, 60)')
})
沒有加上動畫時,按下移動的按鈕會發現正方型很生硬地直接跳到[140,60]的位置
救命!!誰想要這麼生硬蠻橫地跳轉啊!?快點加上動畫緩和一下
// 移動事件觸發
document.querySelector('.moveBtn').addEventListener('click', function(){
rect.transition() // 加上動畫
.attr('transform', 'translate(140, 60)')
})
總算好多了~~這個就是我們要的平滑轉換動畫效果
但我覺得這個動畫有點太快了,想進行一些特別設定,例如:我想要設定動畫的時長是五秒鐘!這時就可以派出 .duration( ) 啦~
// 移動事件觸發
document.querySelector('.moveBtn').addEventListener('click', function(){
rect.transition()
.duration(5000) // 設定動畫時間持續5秒鐘
.attr('transform', 'translate(140, 60)')
})
完成的效果就像這樣
變換顏色
除了移動之外,顏色的變化、邊框粗細等等也都可以用動畫來調整
// html
<svg class="changeColor"></svg>
<br>
<button type="button" class="btn btn-primary changeBtn">改變顏色</button>
// 改變顏色
const round = d3.select('.changeColor')
.append('circle')
.attr('cx', 100)
.attr('cy', 50)
.attr('r', 25)
.attr('fill', 'orange')
.attr('stroke-width', '0.5px')
.attr('stroke', 'black')
document.querySelector('.changeBtn').addEventListener('click', function(){
round.transition()
.duration(1000)
.attr('fill', 'green')
.attr('stroke-width', '6px')
.attr('stroke', 'red')
})
大功告成~transition( ) 是不是很簡單呀?但要特別注意的是,想加上動畫的項目一定要放在 transition( ) 之後,如果放在 transition( ) 之前的話,就無法綁定動畫效果
.delay( ) 動畫延遲
我自己覺得 transition.delay( ) 這個方法很有趣,它的參數是填入想秒數,但也可以填入方法去各別設定每個元素的延遲秒數,達到元素一個接一個進行動畫的效果,就像這樣:
那具體該怎麼做呢?話不多說直接上程式碼吧!
一開始一樣先設定我們的資料,並把資料綁定到 DOM 上
// html
<svg class="delay"></svg>
<button type="button" class="btn btn-primary mt-3 delayBtn">delay開始</button>
// js
const dataDelay = [160, 140, 120, 100, 80, 60 ,40 ,20]
const delay = d3.select('.delay')
.selectAll('circle')
.data(dataDelay)
.enter()
.append('circle')
.attr('cx', d => d)
.attr('cy', 30)
.attr('r', 15)
.attr('fill', 'blue')
.attr('opacity', '0.5')
接著設定按下按鈕時觸發的動畫事件,並且設定 .delay( ) 要一一進行
document.querySelector('.delayBtn').addEventListener('click', function(){
delay.transition()
.delay((d,i)=> i*200) // 分別延遲
.attr('cx', d => d+120) // 位移距離
})
這樣就完成啦~~
.ease( ) 動畫效果
我們接著看到另外一個有趣的API,transition.ease( ) 的參數必須是一個方法,用來設定動畫每一幀的時長,藉此達到不同的動畫效果。但設定動畫運作方式其實蠻複雜的,還好 D3.js 已經幫我們設定好多種不同的動畫方法了!這些方法有:
我們只要找到想要的方法並帶入就可以~想更深入了解這些方法的人可以看官網對於每個方法的的詳細解說
但光看官方文件實在很難理解這些動畫效果,所以我們直接用畫面來展示一下吧!首先,一樣先在畫面上畫個圓
// html
<svg class="ease"></svg>
<select name="ease" id="ease">
<option value=""></option>
</select>
<button type="button" class="btn btn-primary mt-3 easeBtn" onClick="updateEast();">Ease開始</button>
// js
const easeDot = d3.select('.ease')
.append('circle')
.attr('cx', 40)
.attr('cy', 40)
.attr('r', 30)
.attr('fill', 'blue')
接著,我們把d3所有的API 叫出來,並抓出屬於 ease 的 API 帶入< option > 中讓我們能夠選取想要的動畫效果
const easeNames = Object.keys(d3).filter(d=>{
return d.slice(0,4) === 'ease' // 抓出所有方法中,名稱內有ease的方法
})
console.log(easeNames)
d3.select('#ease')
.selectAll('option')
.data(easeNames)
.join('option')
.attr('value', function(d) { return d; })
.text(function(d) { return 'd3.' + d; });
最後,我們設定按下按鈕時,要運行目前選到的動畫效果
function updateEast(){
let easeName = d3.select('#ease').node().value;
console.log(easeName)
easeDot.attr('cx', 40) // 回原點
.transition()
.ease(d3[easeName]) // 設定動畫效果
.attr('cx', 200)
}
完成!!我們來玩玩看吧~
transition.on( ) 動畫的生命週期
前面示範的動畫效果都是被特定指令觸發止後只執行一次,但如果想製作重複執行、不停循環的動畫
該怎麼辦呢?這時我們就可以利用 transition.on( ) 產生的四個事件來進行設計。
transition.on( ) 會產生四種事件:
我們使用 start 這個事件來製作一個無限循環的動畫。 首先,一樣先在畫面上建立一個藍色圓點點
// html
<svg class="loopAnimation"></svg>
//js
const loop = d3.select('.loopAnimation')
.append('circle')
.attr('cx', 50)
.attr('cy', 50)
.attr('r', 25)
.attr('fill', 'blue')
接著,將這個圓點加上transition.on( ),並設定要觸發的動畫事件為"start",它的callback function 叫做 goRight。這邊的意思就是:每當動畫開始時,我要執行 goRight 這個方法
const loop = d3.select('.loopAnimation')
.append('circle')
.attr('cx', 50)
.attr('cy', 50)
.attr('r', 25)
.attr('fill', 'blue')
.transition()
.duration(2000)
.on('start', goRight)
然後我們來設定 goRight 方法吧!
function goRight(){
d3.active(this)
.attr('cx', 200)
.transition()
.on("start", goLeft);
}
接著我們一樣來設定 goLeft 方法
function goLeft (){
d3.active(this)
.attr('cx', 50)
.transition()
.on("start", goRight)
}
我們利用 transition() start 的事件,設定了 goRight、goLeft 兩個方法,並讓這兩個方法互相呼叫,這樣一來就能成功做出無限循環的動畫啦!
看完上面的例子,是不是覺得d3.transition( ) 很有趣呢?最後我們實際示範一個常見的圖表動畫:當資料變動時,圖表要搭配動畫去改變顏色跟高度吧!
// html
<h5 class="mt-5">3.完整圖表動畫</h5>
<div class="chartContainer"></div>
<button type="button" id="start" class="btn btn-primary">動畫開始</button>
//js
//定義兩個資料,都是Y值,x值就用陣列索引即可
let data1 = [150, 122, 133, 161, 116, 139, 143, 115, 193, 137, 122, 141];
let data2 = [180, 146, 180, 172, 133, 149, 152, 138, 188, 192, 117, 146];
let n = data1.length, //資料點的數量
mx = d3.max(d3.merge([data1, data2])) //抓兩個陣列的最大值
// svg
const svg = d3.select('.chartContainer').append('svg');
svg.attr('width', 500)
.attr('height', 300);
svg.selectAll('rect')
.data(data1)
.enter()
.append('rect')
.attr('x', 0)
.attr('y', (d, i) => i * 30)
.attr('width', (d, i) => d)
.attr('height', 20)
.attr('fill', 'orange')
// 動畫開始
d3.select('#start')
.on('click', function () {
svg.selectAll('rect')
.data(data2) // 資料變化
.transition() // 加上動畫
.duration(1000) //點擊之後每個 bar 會在1秒內到達更新位置
.delay((d, i) => 200 * i) //每個bar在分別delay後才開始動畫,
// delay 時間乘上index,做出從上至下更新的動畫效果
.attr('width', (d, i) => d)
.attr('fill', '#66f9ff')
});
實際呈現是這樣子
這就是 d3.js 的動畫效果啦~有興趣的人也可以自己做一次看看!明天我們就要進入另一個有趣的領域囉~
這邊附上本章的程式碼與圖表 Github 、 Github,需要的人請自行取用~